package cn.coder.toolkit;

import lombok.Getter;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CMap<K, V> extends HashMap<K, V> {

    public static void main(String[] args) {
        CMap<String, Object> map = CMap.of("k1", "v1", "k2", new Object(), "k3", 3);
        CMap<Object, Object> objectObjectCMap = CMap.of();
    }

    public CMap() {}

    public CMap(Map<? extends K, ? extends V> m) {
        super(m);
    }

    public static <K, V> CMap<K, V> of() {
        return new CMap<>();
    }

    public static <K, V> CMap<K, V> of(K k1, V v1) {
        CMap<K, V> map = new CMap<>();
        map.put(k1, v1);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2) {
        CMap<K, V> map = CMap.of(k1, v1);
        map.put(k2, v2);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2);
        map.put(k3, v3);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2, k3, v3);
        map.put(k4, v4);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2, k3, v3, k4, v4);
        map.put(k5, v5);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
        map.put(k6, v6);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6);
        map.put(k7, v7);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7);
        map.put(k8, v8);
        return map;
    }

    public static <K, V> CMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {
        CMap<K, V> map = CMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8);
        map.put(k9, v9);
        return map;
    }

    public static CMap<String, Object> create() {
        return new CMap<>();
    }

    public static <T> CMap<String, Object> toMap(T bean, boolean ignoreNullField) {
        try {
            if (null == bean) {
                return null;
            }
            CMap<String, Object> cmap = new CMap<>();
            Class<?> clazz = bean.getClass();
            do {
                Field[] array = clazz.getDeclaredFields();
                for (Field field : array) {
                    field.setAccessible(true);
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    Object value = field.get(bean);
                    if (null != value || !ignoreNullField) {
                        cmap.put(field.getName(), value);
                    }
                }
                clazz = clazz.getSuperclass();
            } while (Object.class != clazz);
            return cmap;
        } catch (Throwable cause) {
            throw new RuntimeException(cause);
        }
    }

    public static <T> CMap<String, Object> toMap(T bean) {
        return toMap(bean, true);
    }

    public static <T> List<CMap<String, Object>> toMap(List<T> list, boolean ignoreNullField) {
        if (null == list) {
            return null;
        }
        List<CMap<String, Object>> data = new ArrayList<>(list.size());
        for (T item : list) {
            data.add(toMap(item, ignoreNullField));
        }
        return data;
    }

    public static <T> List<CMap<String, Object>> toMap(List<T> list) {
        return toMap(list, true);
    }

    @SuppressWarnings("unchecked")
    public <T> T toBean(Class<? super T> clazz) {
        try {
            Constructor<? super T> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object object = constructor.newInstance();
            do {
                Field[] array = clazz.getDeclaredFields();
                for (Field field : array) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    field.setAccessible(true);
                    field.set(object, get(field.getName()));
                }
                clazz = clazz.getSuperclass();
            } while (Object.class != clazz);
            return (T) object;
        } catch (Throwable cause) {
            throw new RuntimeException(cause);
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T get(Object key, Class<T> clazz) {
        return (T) get(key);
    }

    /**
     * 仅仅是让IDE能生成正确的返回值类型, 用法如下
     * TypeReference<List<String> type = new CMap.TypeReference<List<String>>() {};
     */
    @SuppressWarnings("unchecked")
    public <T> T get(Object key, TypeReference<T> typeReference) {
        return (T) get(key);
    }

    public Byte getByte(Object key) {
        return get(key, Byte.class);
    }
    public Short getShort(Object key) {
        return get(key, Short.class);
    }
    public Integer getInteger(Object key) {
        return get(key, Integer.class);
    }
    public Long getLong(Object key) {
        return get(key, Long.class);
    }
    public Float getFloat(Object key) {
        return get(key, Float.class);
    }
    public Double getDouble(Object key) {
        return get(key, Double.class);
    }
    public Character getCharacter(Object key) {
        return get(key, Character.class);
    }
    public Boolean getBoolean(Object key) {
        return get(key, Boolean.class);
    }
    public String getString(Object key) {
        return get(key, String.class);
    }
    public byte getByteValue(Object key) {
        return get(key, byte.class);
    }
    public short getShortValue(Object key) {
        return get(key, short.class);
    }
    public int getIntValue(Object key) {
        return get(key, int.class);
    }
    public long getLongValue(Object key) {
        return get(key, long.class);
    }
    public float getFloatValue(Object key) {
        return get(key, float.class);
    }
    public double getDoubleValue(Object key) {
        return get(key, double.class);
    }
    public char getCharValue(Object key) {
        return get(key, char.class);
    }
    public boolean getBooleanValue(Object key) {
        return get(key, boolean.class);
    }
    public List<String> getStringList(Object key) {
        return get(key, new TypeReference<List<String>>() {});
    }

    // 参考FastJson的TypeReference来获取泛型类型
    @Getter
    public static class TypeReference<T> {

        private final Type type;

        public TypeReference() {
            Type superClass = this.getClass().getGenericSuperclass();
            this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
        }

    }

}
